package util;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;

import objects.AdapletConstants;
import objects.AdapletConstants.ColumnType;
import objects.IObject;
import objects.MappingFile;
import objects.Table;
import sections.FileSection;
import sections.InterfaceSection;
import sections.ReferenceTypeSection;
import sections.RelToRelSection;
import sections.RelationshipSection;
import sections.TableSection;
import sections.TypeSection;

public class MapFileParser
{
	private enum OUTER_STATE {
		EXTERNAL, TABLE, TYPECHECK, TYPE, REFTYPE, REL, RELTOREL, INTERFACE, FILE, ERROR
	};

	private enum INNER_STATE {
		HEADER, POST_HEADER, NONE
	};

	private OUTER_STATE currentState;
	private INNER_STATE currentSubState;

	private String fMapFileName;
	private File fFile;
	private BufferedReader fFileReader;

	private MappingFile fMap;
	private TableSection fTableSection;
//	private TableJoinSection fTableJoinSection;
	private TypeSection fTypeSection;
	private ReferenceTypeSection fReferenceTypeSection;
	private RelationshipSection fRelationshipSection;
	private RelToRelSection fRelToRelSection;
	private InterfaceSection fInterfaceSection;
	private FileSection fFileSection;

	private ArrayList<String> typeCheckBuffer;

	private String errorMessage;
	private IObject currentObject;

	public MapFileParser(String fileName)
	{
		currentState = OUTER_STATE.EXTERNAL;
		currentSubState = INNER_STATE.NONE;

		try
		{
			fFile = new File(fileName);
			fFileReader = new BufferedReader(new FileReader(fFile));

			fMapFileName = fFile.getName();
			fMap = new MappingFile(fMapFileName);

			fTableSection = fMap.getTableSection();
//			fTableJoinSection = fMap.getTableJoinSection();
			fTypeSection = fMap.getTypeSection();
			fReferenceTypeSection = fMap.getReferenceTypeSection();
			fRelationshipSection = fMap.getRelationshipSection();
			fRelToRelSection = fMap.getRelToRelSection();
			fInterfaceSection = fMap.getInterfaceSection();
			fFileSection = fMap.getFileSection();

			typeCheckBuffer = null;

		} catch (Exception e)
		{
			System.out.println("Exception");
			e.printStackTrace();
		}
	}

	public MappingFile parseMapFile()
	{

		try
		{
			String currentLine = fFileReader.readLine();

			while (currentLine != null)
			{
				currentLine = currentLine.trim();

				if (isCommentLine(currentLine))
				{
					continue;
				}

				switch (currentState)
				{
				case EXTERNAL:
					parseExternalLine(currentLine); //DONE
					break;
				case TABLE:
					parseTableLine(currentLine); //DONE
					break; 
				case TYPECHECK:
					//This should probably be an error.
					checkIfRefType(currentLine); //DONE
					break;
				case TYPE:
					parseTypeLine(currentLine);
					break;
				case REFTYPE:
					parseRefTypeLine(currentLine);
					break;
				case REL:
					parseRelLine(currentLine);
					break;
				case RELTOREL:
					parseRelToRelLine(currentLine);
					break;
				case INTERFACE:
					parseInterfaceLine(currentLine);
					break;
				case FILE:
					parseFileLine(currentLine); //DONE
					break;
				case ERROR:
					System.out.println(errorMessage);
					return null;
				}

				currentLine = fFileReader.readLine();
			}

		} catch (IOException e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		return fMap;
	}

	private void checkIfRefType(String currentLine) throws IOException
	{
		boolean checking = true;
		
		while(checking)
		{
			typeCheckBuffer.add(currentLine);
			
			if(currentSubState == INNER_STATE.HEADER)
			{
				currentLine = fFileReader.readLine();
				currentSubState = INNER_STATE.POST_HEADER;
			}
			else if(isEndOfBlock(currentLine))
			{
				currentState = OUTER_STATE.TYPE;
				checking = false;
			}
			else
			{
				String[] words = new String[2];
				parseOutWords(currentLine, words);
				
				if(words[0].equals("reference") && words[1].equals("true"))
				{
					currentState = OUTER_STATE.REFTYPE;
					checking = false;
				}
				else
				{
					currentLine = fFileReader.readLine();
				}
			}
		}
		
		currentSubState = INNER_STATE.HEADER;
		switch (currentState)
		{
		case TYPE:
			parseTypeLine(null);
			break;
		case REFTYPE:
			parseRefTypeLine(null);
			break;
			
		default:
			break;
		}
		
		
	}

	private void parseRefTypeLine(String currentLine)
	{
		if (isEndOfBlock(currentLine))
		{
			currentState = OUTER_STATE.EXTERNAL;
			currentSubState = INNER_STATE.NONE;
			currentObject = null;
			return;
		}
		
		
		if(currentLine == null)
		{
			
		}
		else
		{
			
		}

	}

	private boolean isCommentLine(String currentLine)
	{
		return currentLine.charAt(0) == '#';
	}

	private boolean isEndOfBlock(String currentLine)
	{
		return currentLine.equals("}");
	}

	private void parseFileLine(String currentLine)
	{
		if (isEndOfBlock(currentLine))
		{
			currentState = OUTER_STATE.EXTERNAL;
			currentSubState = INNER_STATE.NONE;
			currentObject = null;
			return;
		}
		
		switch(currentSubState)
		{
		case HEADER:
			createNewFile(currentLine);
			break;
		case POST_HEADER:
			String firstWord = getFirstWord(currentLine);
			if(firstWord.equals("baseobject"))
			{
				setFileBaseObject(currentLine);
			}
			else 
			{
				String columnName = parseOutColumnName(currentLine);
				
				if(firstWord.equals("alias"))
				{
					((objects.File) currentObject).setAlias(columnName);
				}
				else if(firstWord.equals("filename"))
				{
					((objects.File) currentObject).setFilename(columnName);
				}
				else if(firstWord.equals("format"))
				{
					((objects.File) currentObject).setFormat(columnName);
				}
				else if(firstWord.equals("size"))
				{
					((objects.File) currentObject).setSize(columnName);
				}
				else if(firstWord.equals("store"))
				{
					((objects.File) currentObject).setStore(columnName);
				}
				else if(firstWord.equals("location"))
				{
					((objects.File) currentObject).setLocation(columnName);
				}
			}
			
			break;
		case NONE:
			break;
		}
	}

	private String parseOutColumnName(String currentLine)
	{
		String[] words = new String[3];
		parseOutWords(currentLine, words);
		
		String tableAndColumn = words[2];
		int startParen = tableAndColumn.indexOf('(');
		int endParen = tableAndColumn.indexOf(')');
		
		String columnName = tableAndColumn.substring(startParen + 1, endParen);
		return columnName;
	}

	private void setFileBaseObject(String currentLine)
	{
		String[] words = new String[4];
		parseOutWords(currentLine, words);
		
		String baseObject = words[1];
		String tableAndIDColumn = words[3];
		
		int startParen = tableAndIDColumn.indexOf('(');
		int endParen = tableAndIDColumn.indexOf(')');
		
		String tableName = tableAndIDColumn.substring(0, startParen);
		String idColName = tableAndIDColumn.substring(startParen + 1, endParen);
		
		baseObject = baseObject.replaceAll("\"", "");
		
		((objects.File) currentObject).setBaseObject(baseObject);
		((objects.File) currentObject).setTable(tableName);
		((objects.File) currentObject).setOID(idColName);
	}

	private void createNewFile(String currentLine)
	{
		String[] words = currentLine.split(" ");

		if (words.length == 3)
		{
			String fileID = words[1];
			String bracket = words[2];

			if (bracket.equals("{"))
			{
				currentObject = fFileSection.createNewFile(fileID);
				currentSubState = INNER_STATE.POST_HEADER;
			}
			else
			{
				currentState = OUTER_STATE.ERROR;
				errorMessage = "Unable to Parse the line: " + currentLine;
			}
		}
		else
		{
			currentState = OUTER_STATE.ERROR;
			errorMessage = "Unable to Parse the line: " + currentLine;
		}
	}
	
	private String getFirstWord(String currentLine)
	{
		return currentLine.split(" ")[0];
	}

	private void parseInterfaceLine(String currentLine)
	{
		if (isEndOfBlock(currentLine))
		{
			currentState = OUTER_STATE.EXTERNAL;
			currentSubState = INNER_STATE.NONE;
			currentObject = null;
			return;
		}
	}

	private void parseRelToRelLine(String currentLine)
	{
		if (isEndOfBlock(currentLine))
		{
			currentState = OUTER_STATE.EXTERNAL;
			currentSubState = INNER_STATE.NONE;
			currentObject = null;
			return;
		}
	}

	private void parseRelLine(String currentLine)
	{
		if (isEndOfBlock(currentLine))
		{
			currentState = OUTER_STATE.EXTERNAL;
			currentSubState = INNER_STATE.NONE;
			currentObject = null;
			return;
		}
	}

	private void parseTypeLine(String currentLine)
	{
		if (isEndOfBlock(currentLine))
		{
			currentState = OUTER_STATE.EXTERNAL;
			currentSubState = INNER_STATE.NONE;
			currentObject = null;
			return;
		}
		
		if(currentLine == null)
		{
			
		}
		else
		{
			
		}
	}

	private void parseTableLine(String currentLine)
	{
		if (isEndOfBlock(currentLine))
		{
			currentState = OUTER_STATE.EXTERNAL;
			currentSubState = INNER_STATE.NONE;
			currentObject = null;
			return;
		}

		switch (currentSubState)
		{
		case HEADER:
			createNewTable(currentLine);
			break;
		case POST_HEADER:
			createTableColumn(currentLine);
			break;
		case NONE:
			break;
		}
	}

	private void createTableColumn(String currentLine)
	{
		String[] words = new String[3];
		parseOutWords(currentLine, words);

		String columnName = words[0];
		String columnType = words[1];

		ColumnType colType = AdapletConstants.convertStringToType(columnType);

		if (colType == null)
		{
			currentState = OUTER_STATE.ERROR;
			errorMessage = "Unable to Parse line: " + currentLine;
			return;
		}

		int largeStringSize = 0;
		if (colType == ColumnType.LARGESTRING)
		{
			try
			{
				largeStringSize = Integer.parseInt(words[2]);
			} catch (Exception e)
			{
				currentState = OUTER_STATE.ERROR;
				errorMessage = "Unable to Parse line: " + currentLine;
				return;
			}
		}

		((Table) currentObject).addColumn(columnName, colType, largeStringSize);

	}

	private void parseOutWords(String currentLine, String[] words)
	{
		int start = 0;
		int end = 0;
		int curWord = 0;
		int maxLengh = currentLine.length();
		
		boolean inQuotes = false;

		for (int i = 0; i < maxLengh; i++)
		{
			char curChar = currentLine.charAt(i);
			if (curChar == ';')
			{
				words[curWord] = currentLine.substring(start, end);
				curWord++;
				break;
			}
			else if (curChar == ' ' || curChar == '\t')
			{
				if(inQuotes)
				{
					end++;
				}
				else if (start == end)
				{
					start++;
					end++;
				}
				else
				{
					words[curWord] = currentLine.substring(start, end);
					curWord++;
					start = ++end;
				}
			}
			else if(curChar == '"')
			{
				inQuotes = !inQuotes;
			}
			else
			{
				end++;
			}
		}
	}

	private void createNewTable(String currentLine)
	{
		String[] words = currentLine.split(" ");

		if (words.length == 3)
		{
			String tableName = words[1];
			String bracket = words[2];

			if (bracket.equals("{"))
			{
				currentObject = fTableSection.createNewTable(tableName);
				currentSubState = INNER_STATE.POST_HEADER;
			}
			else
			{
				currentState = OUTER_STATE.ERROR;
				errorMessage = "Unable to Parse the line: " + currentLine;
			}
		}
		else
		{
			currentState = OUTER_STATE.ERROR;
			errorMessage = "Unable to Parse the line: " + currentLine;
		}

	}

	private void parseExternalLine(String currentLine) throws IOException
	{
		String[] words = currentLine.split(" ");

		String firstWord = words[0];

		if (firstWord.equals("schema"))
		{
			setSchemaName(currentLine, words);
		}
		else if (firstWord.equals("item"))
		{
			String[] wordsArray = new String[3];
			parseOutWords(currentLine, wordsArray);
			
			String key = wordsArray[1];
			String value = wordsArray[2];
			
			if(key.equals("ExportFieldDelimiter"))
			{
				fMap.setFieldDelimiter(value);
			}
			else if(key.equals("ExportRecordDelimiter"))
			{
				fMap.setRecordDelimiter(value);
			}
			else if(key.equals("ExportTagDBPath"))
			{
				fMap.setTagDB(value);
			}
			else if(key.equals("ExportAdminTagDBPath"))
			{
				fMap.setTagAdmin(value);
			}
			else if(key.equals("LicenseKey"))
			{
				fMap.setLicense(value);
			}
			else if(key.equals("MaxNotFoundReport"))
			{
				fMap.setMaxNotFound(value);
			}
			else
			{
				fMap.addAdditionalItem(key, value);
			}
		}
		else if (firstWord.equals("mode"))
		{
			setMode(currentLine, words);
		}
		else if (firstWord.equals("table"))
		{
			currentState = OUTER_STATE.TABLE;
			currentSubState = INNER_STATE.HEADER;
			parseTableLine(currentLine);
		}
		else if (firstWord.equals("type"))
		{
			currentState = OUTER_STATE.TYPECHECK;
			currentSubState = INNER_STATE.HEADER;
			typeCheckBuffer = new ArrayList<String>();
			checkIfRefType(currentLine);
		}
		else if (firstWord.equals("relationship"))
		{
			currentState = OUTER_STATE.REL;
			currentSubState = INNER_STATE.HEADER;
			parseRelLine(currentLine);
		}
		else if (firstWord.equals("reltorel"))
		{
			currentState = OUTER_STATE.RELTOREL;
			currentSubState = INNER_STATE.HEADER;
			parseRelToRelLine(currentLine);
		}
		else if (firstWord.equals("interface"))
		{
			currentState = OUTER_STATE.INTERFACE;
			currentSubState = INNER_STATE.HEADER;
			parseInterfaceLine(currentLine);
		}
		else if (firstWord.equals("file"))
		{
			currentState = OUTER_STATE.FILE;
			currentSubState = INNER_STATE.HEADER;
			parseFileLine(currentLine);
		}
		else
		{
			errorMessage = "Unable to Parse the line: " + currentLine;
			currentState = OUTER_STATE.ERROR;
		}

	}

	private void setMode(String currentLine, String[] words)
	{
		if (words.length == 2)
		{
			String secondWord = words[1];
			if (secondWord.endsWith(";"))
			{
				String mode = secondWord.substring(0, secondWord.length() - 2);
				if (mode.equals("readonly") || mode.equals("readwrite") || mode.equals("migrate")
						|| mode.equals("extend"))
				{
					fMap.setMode(mode);
				}
				else
				{
					errorMessage = "Invalid Mode specified: " + currentLine;
					currentState = OUTER_STATE.ERROR;
				}
			}
			else
			{
				errorMessage = "Unable to Parse the line: " + currentLine;
				currentState = OUTER_STATE.ERROR;
			}
		}
		else
		{
			errorMessage = "Unable to Parse the line: " + currentLine;
			currentState = OUTER_STATE.ERROR;
		}
	}

	private void setSchemaName(String currentLine, String[] words)
	{
		if (words.length == 2)
		{
			String secondWord = words[1];
			if (secondWord.endsWith(";"))
			{
				String schemaName = secondWord.substring(0, secondWord.length() - 2);
				fMap.setSchema(schemaName);
			}
			else
			{
				errorMessage = "Unable to Parse the line: " + currentLine;
				currentState = OUTER_STATE.ERROR;
			}
		}
		else
		{
			errorMessage = "Unable to Parse the line: " + currentLine;
			currentState = OUTER_STATE.ERROR;
		}
	}

}
